﻿//--------------------------------------------------------------------------
// 
//  Copyright (c) Chili Software.  All rights reserved. 
// 
//  File: MappingContainer.cs
//
//  Description: Contains the mappings for all persistent objects.
// 
//--------------------------------------------------------------------------

using System;
using System.Collections.Generic;
using Chili.Opf3.Mapping.Providers;

namespace Chili.Opf3.Mapping
{
	/// <summary>
	/// Contains the all entity-object mappings of the persistent object types.
	/// </summary>
	internal static class MappingContainer
	{
		private static readonly object LOCK = new object();

		private static IDictionary<Type, TypeMapping> _mappings = new Dictionary<Type, TypeMapping>();
		private static TypeMapping _lastMapping;
		private static IDictionary<Type, Type> _interfacePersistents = new Dictionary<Type, Type>();

		/// <summary>
		/// Returns the mapping for a given object. If the mapping does not exist it is created by this routine.
		/// </summary>
		/// <param name="obj">The object the mapping is returned.</param>
		public static TypeMapping GetTypeMapping(object obj)
		{
			if (obj == null)
				throw new ArgumentNullException("obj");

			return GetTypeMapping(obj.GetType());
		}

		/// <summary>
		/// Returns the mapping for the given persistent type. If the mapping does not exist it is 
		/// created by this routine.
		/// </summary>
		/// <param name="type">Type of object the mapping is returned.</param>
		public static TypeMapping GetTypeMapping(Type type)
		{
			if (type == null)
				throw new ArgumentNullException("type");

			// Get the real persistent type.
			type = GetPersistentType(type);

			TypeMapping m = null;
			lock (LOCK)
			{
				// Return the last mapping, if the type equals.
				if (_lastMapping != null && _lastMapping.PersistentType == type)
					return _lastMapping;

				if (!_mappings.TryGetValue(type, out m))
				{
					// Create a new mapping and add it to the list of mappings.
					m = MappingBuilder.CreatePersistentMapping(type);
					_mappings.Add(type, m);
				}

				_lastMapping = m;
			}
			// Return the mapping.
			return m;
		}

		/// <summary>
		/// Gets the persistent type from the given type.
		/// </summary>
		/// <param name="type">The type that's persistent type is returned.</param>
		public static Type GetPersistentType(Type type)
		{
			if (type == null)
				throw new ArgumentNullException("type");

			// If not an interface return the given type.
			if (!type.IsInterface)
				return type;

			// Get the persistent type for the interface.
			Type t = null;
			if (_interfacePersistents.TryGetValue(type, out t))
				return t;

			// Throw an exception if the interface is not registered with a persistent.
			throw new MappingException(string.Format("There is no persistent type registered for the interface type: {0}.", type.FullName));
		}

		/// <summary>
		/// Registers the given interface type with the persistent object type.
		/// </summary>
		/// <param name="interfaceType">The type of the interface registered with the persistent type.</param>
		/// <param name="persistentType">The type of the persistent registered with the interface type.</param>
		internal static bool RegisterPersistentInterface(Type interfaceType, Type persistentType)
		{
			if (persistentType == null)
				throw new ArgumentNullException("persistentType");
			if (interfaceType == null)
				throw new ArgumentNullException("interfaceType");

			// Validate the arguments.
			if (!interfaceType.IsInterface)
				throw new ArgumentException("Interface type is no interface.", "interfaceType");
			if (persistentType.IsInterface)
				throw new ArgumentException("Persistent type is an interface.", "persistentType");

			// Check if already in the list.
			if (_interfacePersistents.ContainsKey(interfaceType))
				return false;

			// Check if the persistent type implements the interface.
			if (!interfaceType.IsAssignableFrom(persistentType))
				throw new ArgumentException(string.Format("The persistent type {0} does not implement the interface type {1}.", 
					persistentType.FullName, interfaceType.FullName));

			// Add the interface and persistent type to the dictionary.
			_interfacePersistents.Add(interfaceType, persistentType);
			return true;
		}
	}
}
